﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using HeapModel.ProgInfo;
using System.Reflection;
using System.Diagnostics.Contracts;

namespace HeapProfiler.Runtime
{
	/// <summary>
	/// A class used to intern type, root, and field identifiers.
	/// </summary>
	public sealed class IdentifierIntern
	{
		internal readonly Dictionary<TypeInfoIdentPair, List<FieldInfo>> InternTypeToRefFieldMap;
		internal readonly Dictionary<TypeInfoIdentPair, int> TypeDataSizeMap;
		
		private readonly Dictionary<TypeInfoIdentPair, TypeIdentifier> m_typemap;
		private readonly Dictionary<FieldInfoIdentPair, OffsetIdentifier> m_offsetmap;
		private readonly Dictionary<TypeInfoIdentPair, OffsetIdentifier> m_collfieldmap;

		private readonly Dictionary<TypeIdentifier, HashSet<TypeIdentifier>> m_subtyperelations;
		
		/// <summary>
		/// Set up the name table initially empty.
		/// </summary>
		public IdentifierIntern()
		{
			this.InternTypeToRefFieldMap = new Dictionary<TypeInfoIdentPair, List<FieldInfo>>();
			this.TypeDataSizeMap = new Dictionary<TypeInfoIdentPair, int>();
			
			this.TypeDataSizeMap.Add(new TypeInfoIdentPair((new System.Boolean()).GetType()), sizeof(System.Boolean));
			this.TypeDataSizeMap.Add(new TypeInfoIdentPair((new System.Char()).GetType()), sizeof(System.Char));
			this.TypeDataSizeMap.Add(new TypeInfoIdentPair((new System.Byte()).GetType()), sizeof(System.Byte));
			this.TypeDataSizeMap.Add(new TypeInfoIdentPair((new System.SByte()).GetType()), sizeof(System.SByte));
			this.TypeDataSizeMap.Add(new TypeInfoIdentPair((new System.IntPtr()).GetType()), 32);
			this.TypeDataSizeMap.Add(new TypeInfoIdentPair((new System.UIntPtr()).GetType()), 32);

			this.TypeDataSizeMap.Add(new TypeInfoIdentPair((new System.Int16()).GetType()), sizeof(System.Int16));
			this.TypeDataSizeMap.Add(new TypeInfoIdentPair((new System.UInt16()).GetType()), sizeof(System.UInt16));
			this.TypeDataSizeMap.Add(new TypeInfoIdentPair((new System.Int32()).GetType()), sizeof(System.Int32));
			this.TypeDataSizeMap.Add(new TypeInfoIdentPair((new System.UInt32()).GetType()), sizeof(System.UInt32));
			this.TypeDataSizeMap.Add(new TypeInfoIdentPair((new System.Int64()).GetType()), sizeof(System.Int64));
			this.TypeDataSizeMap.Add(new TypeInfoIdentPair((new System.UInt64()).GetType()), sizeof(System.UInt64));

			this.TypeDataSizeMap.Add(new TypeInfoIdentPair((new System.Single()).GetType()), sizeof(System.Single));
			this.TypeDataSizeMap.Add(new TypeInfoIdentPair((new System.Double()).GetType()), sizeof(System.Double)); 

			this.m_typemap = new Dictionary<TypeInfoIdentPair, TypeIdentifier>();
			this.m_offsetmap = new Dictionary<FieldInfoIdentPair, OffsetIdentifier>();
			this.m_collfieldmap = new Dictionary<TypeInfoIdentPair, OffsetIdentifier>();

			this.m_subtyperelations = new Dictionary<TypeIdentifier, HashSet<TypeIdentifier>>();
		}

		/// <summary>
		/// Load in all the target types/subtypes for each offset that has been interned.
		/// </summary>
		public void CompleteOffsetInfo()
		{
			foreach(OffsetIdentifier offid in this.m_offsetmap.Values)
			{
				HashSet<TypeIdentifier> subtl = new HashSet<TypeIdentifier>();
				foreach(TypeIdentifier tt in offid.OffsetTypes)
					subtl.UnionWith(this.m_subtyperelations[tt]);

				offid.OffsetTypes.UnionWith(subtl);
			}

			foreach(OffsetIdentifier offid in this.m_collfieldmap.Values)
			{
				HashSet<TypeIdentifier> subtl = new HashSet<TypeIdentifier>();
				foreach(TypeIdentifier tt in offid.OffsetTypes)
					subtl.UnionWith(this.m_subtyperelations[tt]);

				offid.OffsetTypes.UnionWith(subtl);
			}
		}

		/// <summary>
		/// A simple helper to determine if a given type is a struct type.
		/// </summary>
		public static bool IsStructType(Type tt)
		{
			Contract.Requires(tt != null);
			return (!tt.IsPrimitive) & (tt.IsValueType);
		}

		/// <summary>
		/// A simple helper to determine if a given type is an object type.
		/// </summary>
		public static bool IsObjectType(Type tt)
		{
			Contract.Requires(tt != null);
			return (tt.IsClass) | (tt.IsInterface);
		}

		/// <summary>
		/// Extract all the member reference fields for a given type, also return the count of all fields that are visible (integer, float, etc.).
		/// </summary>
		private static void ExtractAllFieldsForType(Type tt, List<FieldInfo> rfs)
		{
			FieldInfo[] tfo = tt.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
			foreach(FieldInfo fi in tfo)
			{
				if(!rfs.Exists((FieldInfo f) => FieldInfoDeclEqual(f, fi)))
					rfs.Add(fi);
			}

			if(tt.BaseType != null)
				ExtractAllFieldsForType(tt.BaseType, rfs);
		}

		private static bool FieldInfoDeclEqual(FieldInfo fi1, FieldInfo fi2)
		{ return fi1.DeclaringType.Equals(fi2.DeclaringType) && fi1.Name.Equals(fi2.Name); }

		internal bool IsTypeNameIntered(Type tname)
		{ return this.m_typemap.ContainsKey(new TypeInfoIdentPair(tname)); }

		internal TypeIdentifier LookupInternedTypeName(Type tname)
		{ return this.m_typemap[new TypeInfoIdentPair(tname)]; }

		public TypeIdentifier InternTypeName(Type tname)
		{
			Contract.Requires(tname != null);

			TypeInfoIdentPair oktp = new TypeInfoIdentPair(tname);
			if(!this.m_typemap.ContainsKey(oktp))
			{
				String ntn = NiceTypeNameString(tname);
				TypeIdentifier intn = new TypeIdentifier(ntn, (IdentifierIntern.IsObjectType(tname) ? TypeIdentifier.TypeCategory.Object : TypeIdentifier.TypeCategory.Value));
				this.m_typemap.Add(oktp, intn);

				if(!this.m_subtyperelations.ContainsKey(intn))
					this.m_subtyperelations.Add(intn, new HashSet<TypeIdentifier>());
				this.m_subtyperelations[intn].Add(intn);

				Type exttt = tname.BaseType;
				while(exttt != null)
				{
					TypeIdentifier nbt = InternTypeName(exttt);
					if(!this.m_subtyperelations.ContainsKey(nbt))
						this.m_subtyperelations.Add(nbt, new HashSet<TypeIdentifier>());
					this.m_subtyperelations[nbt].Add(intn);

					exttt = exttt.BaseType;
				}

				Type[] intff = tname.GetInterfaces();
				foreach(Type intft in intff)
				{
					TypeIdentifier nit = InternTypeName(intft);
					if(!this.m_subtyperelations.ContainsKey(nit))
						this.m_subtyperelations.Add(nit, new HashSet<TypeIdentifier>());
					this.m_subtyperelations[nit].Add(intn);
				}

				if(!tname.IsInterface)
					this.AddPtrFieldInfo(intn, tname);
			}

			return this.m_typemap[oktp];
		}

		private void AddPtrFieldInfo(TypeIdentifier intn, Type tt)
		{
			Type[] intff = tt.GetInterfaces();

			bool islist = (tt.Namespace != null && tt.Namespace.StartsWith("System.Collections")) && tt.Name.StartsWith("List");
			bool isset = (tt.Namespace != null && tt.Namespace.StartsWith("System.Collections")) && tt.Name.StartsWith("HashSet");
			bool iskeyvalpair = (tt.Namespace != null && tt.Namespace.StartsWith("System.Collections")) && tt.Name.StartsWith("KeyValuePair");
			bool isdict = (tt.Namespace != null && tt.Namespace.StartsWith("System.Collections")) && tt.Name.StartsWith("Dictionary");

			if(tt.IsArray)
			{
				intn.TypeCategoryTag = TypeIdentifier.TypeCategory.Array;
				Type contentstype = tt.GetElementType();
				if(IdentifierIntern.IsObjectType(contentstype) || IdentifierIntern.IsStructType(contentstype))
				{
					TypeIdentifier ftype = InternTypeName(contentstype);
					String cnts = NiceTypeNameString(tt) + ":[]";

					TypeInfoIdentPair tfip = new TypeInfoIdentPair(tt);
					if(!this.m_collfieldmap.ContainsKey(tfip))
					{
						OffsetIdentifier coid = new OffsetIdentifier(cnts, new HashSet<TypeIdentifier>());
						coid.OffsetTypes.Add(ftype);
						this.m_collfieldmap.Add(tfip, coid);
					}

					OffsetIdentifier ttn = this.m_collfieldmap[tfip];
					intn.AllOffsetIdentifiers.Add(ttn);
				}
			}
			else if(islist | isset)
			{
				if(islist)
					intn.TypeCategoryTag = TypeIdentifier.TypeCategory.List;
				else if(isset)
					intn.TypeCategoryTag = TypeIdentifier.TypeCategory.Set;
				else
					throw new InvalidOperationException();

				MethodInfo[] miia = tt.GetMethods();
				MethodInfo mienum = miia.FirstOrDefault((MethodInfo mi) => mi.Name.Equals("GetEnumerator"));
				PropertyInfo pcurrent = mienum.ReturnType.GetProperty("Current");

				Type contentstype = pcurrent.PropertyType;
				if(IdentifierIntern.IsObjectType(contentstype) || IdentifierIntern.IsStructType(contentstype))
				{
					TypeIdentifier ftype = InternTypeName(contentstype);
					String cnts = NiceTypeNameString(tt) + ":[]";
					TypeInfoIdentPair tfip = new TypeInfoIdentPair(tt);
					if(!this.m_collfieldmap.ContainsKey(tfip))
					{
						OffsetIdentifier coid = new OffsetIdentifier(cnts, new HashSet<TypeIdentifier>());
						coid.OffsetTypes.Add(ftype);
						this.m_collfieldmap.Add(tfip, coid);
					}

					OffsetIdentifier ttn = this.m_collfieldmap[tfip];
					intn.AllOffsetIdentifiers.Add(ttn);
				}
			}
			else if(iskeyvalpair)
			{
				intn.TypeCategoryTag = TypeIdentifier.TypeCategory.KeyValPair;
				List<FieldInfo> fos = new List<FieldInfo>();
				ExtractAllFieldsForType(tt, fos);
				int totalsizeoftype = 0;

				foreach(FieldInfo fi in fos)
				{
					if(IdentifierIntern.IsObjectType(fi.FieldType) || IdentifierIntern.IsStructType(fi.FieldType))
					{
						TypeIdentifier ftt = this.InternTypeName(fi.FieldType);
						OffsetIdentifier frn = this.InternMemberField(fi, ftt);

						if(IdentifierIntern.IsObjectType(fi.FieldType) || IdentifierIntern.IsStructType(fi.FieldType))
							intn.AllOffsetIdentifiers.Add(frn);
					}

					if(IdentifierIntern.IsObjectType(fi.FieldType))
						totalsizeoftype += 4;
					else
						totalsizeoftype += this.TypeDataSizeMap[new TypeInfoIdentPair(fi.FieldType)];
				}

				TypeInfoIdentPair oktp = new TypeInfoIdentPair(tt);
				InternTypeToRefFieldMap.Add(oktp, fos);

				this.TypeDataSizeMap.Add(oktp, totalsizeoftype);
			}
			else if(isdict)
			{
				MethodInfo mienum = tt.GetMethod("GetEnumerator");
				PropertyInfo pcurrent = mienum.ReturnType.GetProperty("Current");
				intn.TypeCategoryTag = TypeIdentifier.TypeCategory.Dictionary;
				Type contentstype = pcurrent.PropertyType;

				TypeIdentifier ftype = InternTypeName(contentstype);
				String cnts = NiceTypeNameString(tt) + ":[]";
				TypeInfoIdentPair tfip = new TypeInfoIdentPair(tt);

				if(!this.m_collfieldmap.ContainsKey(tfip))
				{
					OffsetIdentifier coid = new OffsetIdentifier(cnts, new HashSet<TypeIdentifier>());
					coid.OffsetTypes.Add(ftype);
					this.m_collfieldmap.Add(tfip, coid);
				}

				OffsetIdentifier ttn = this.m_collfieldmap[tfip];
				intn.AllOffsetIdentifiers.Add(ttn);
			}
			else if(tt.Namespace != null && tt.Namespace.Equals("System") && tt.Name.Equals("String"))
			{
				;
			}
			else
			{
				List<FieldInfo> fos = new List<FieldInfo>();
				int totaldatasizeoftype = 0;

				if(intn.NameValue.StartsWith("System.Reflection"))
					totaldatasizeoftype = 0;
				else
				{
					ExtractAllFieldsForType(tt, fos);
					foreach(FieldInfo fi in fos)
					{
						if(IdentifierIntern.IsObjectType(fi.FieldType) || IdentifierIntern.IsStructType(fi.FieldType))
						{
							TypeIdentifier ftt = this.InternTypeName(fi.FieldType);
							OffsetIdentifier frn = this.InternMemberField(fi, ftt);

							if(IdentifierIntern.IsObjectType(fi.FieldType) || IdentifierIntern.IsStructType(fi.FieldType))
								intn.AllOffsetIdentifiers.Add(frn);
						}

						if(IdentifierIntern.IsObjectType(fi.FieldType))
							totaldatasizeoftype += 4;
						else
							totaldatasizeoftype += this.TypeDataSizeMap[new TypeInfoIdentPair(fi.FieldType)];
					}
				}

				TypeInfoIdentPair oktp = new TypeInfoIdentPair(tt);
				InternTypeToRefFieldMap.Add(oktp, fos);

				if(!this.TypeDataSizeMap.ContainsKey(oktp))
					this.TypeDataSizeMap.Add(oktp, totaldatasizeoftype);
			}
		}

		internal OffsetIdentifier LookupInternedFieldName(FieldInfo f)
		{ return this.m_offsetmap[new FieldInfoIdentPair(f)]; }

		internal OffsetIdentifier LookupInternedCollectionFieldName(Type tt)
		{ return this.m_collfieldmap[new TypeInfoIdentPair(tt)]; }

		private OffsetIdentifier InternMemberField(FieldInfo f, TypeIdentifier ftt)
		{
			FieldInfoIdentPair okfp = new FieldInfoIdentPair(f);
			if(!this.m_offsetmap.ContainsKey(okfp))
			{
				InternTypeName(f.DeclaringType);

				String cnts = NiceTypeNameString(f.DeclaringType) + ":" + f.Name;
				OffsetIdentifier intn = new OffsetIdentifier(cnts, new HashSet<TypeIdentifier>());
				intn.OffsetTypes.Add(ftt);
				this.m_offsetmap.Add(okfp, intn);
			}

			return this.m_offsetmap[okfp];
		}

		/////////////////////
		//Generate various field and type names, we need to generate the same names when reading System.Type and CCI.IType.. stuff

		/// <summary>
		/// Return a list of all the type identifiers suitable for dumping.
		/// </summary>
		internal List<TypeIdentifier> AllTypesForDump()
		{ return new List<TypeIdentifier>(this.m_typemap.Values); }

		/// <summary>
		/// Return a list of all the field identifiers suitable for dumping.
		/// </summary>
		internal List<OffsetIdentifier> AllFieldForDump()
		{
			List<OffsetIdentifier> rfl = new List<OffsetIdentifier>();
			rfl.AddRange(this.m_offsetmap.Values);
			rfl.AddRange(this.m_collfieldmap.Values);

			return rfl;
		}

		/// <summary>
		/// Translate a System.Type to a nicer string version.
		/// </summary>
		internal static String NiceTypeNameString(Type tt)
		{
			StringBuilder nsb = new StringBuilder();

			if((!tt.IsGenericParameter) && tt.DeclaringType != null)
			{
				nsb.Append(NiceTypeNameString(tt.DeclaringType) + ".");
			}
			else
			{
				if(!String.IsNullOrEmpty(tt.Namespace))
					nsb.Append(tt.Namespace + ".");
			}

			String tkn = tt.Name;
			int tkl = tkn.IndexOf('`');
			if(tkl != -1)
				tkn = tkn.Substring(0, tkl);

			nsb.Append(tkn);

			if(tt.IsGenericType)
			{
				Type[] gp = tt.GetGenericArguments();
				nsb.Append("<");
				for(int i = 0; i < gp.Length; ++i)
				{
					if(i != 0)
						nsb.Append(",");
					String ttfn = NiceTypeNameString(gp[i]);
					nsb.Append(ttfn);
				}
				nsb.Append(">");
			}

			String tr = nsb.ToString();
			return tr;
		}
	}

	/// <summary>
	/// Since the reflected fieldinfo objects don't do equality nicely we implement this to do it.
	/// </summary>
	struct FieldInfoIdentPair : IEquatable<FieldInfoIdentPair>
	{
		private readonly FieldInfo f; //just for convinience of debugging
		private readonly int fmetadata;
		private readonly Module fhomemod;

		public FieldInfoIdentPair(FieldInfo fif)
		{
			Contract.Requires(fif != null);
			this.f = fif;
			this.fmetadata = fif.MetadataToken;
			this.fhomemod = fif.Module;
		}

		public override String ToString()
		{ return this.f.ToString(); }

		public override int GetHashCode()
		{ return this.fmetadata; }

		public bool Equals(FieldInfoIdentPair other)
		{ return this.fmetadata == other.fmetadata && this.fhomemod == other.fhomemod; }

		public override bool Equals(Object obj)
		{ return (obj is FieldInfoIdentPair) && this.Equals((FieldInfoIdentPair)obj); }

		public static bool operator ==(FieldInfoIdentPair lhs, FieldInfoIdentPair rhs)
		{ return lhs.Equals(rhs); }

		public static bool operator !=(FieldInfoIdentPair lhs, FieldInfoIdentPair rhs)
		{ return !(lhs.Equals(rhs)); }
	}

	/// <summary>
	/// Since the reflected type objects don't do equality like we want we implement this to do it.
	/// </summary>
	struct TypeInfoIdentPair : IEquatable<TypeInfoIdentPair>
	{
		private readonly Type tt; //just for convinience of debugging

		public TypeInfoIdentPair(Type t)
		{
			Contract.Requires(t != null);
			this.tt = t;
		}

		public override String ToString()
		{ return this.tt.ToString(); }

		public override int GetHashCode()
		{ return this.tt.GetHashCode(); }

		public bool Equals(TypeInfoIdentPair other)
		{ return this.tt == other.tt; }

		public override bool Equals(Object obj)
		{ return (obj is TypeInfoIdentPair) && this.Equals((TypeInfoIdentPair)obj); }

		public static bool operator ==(TypeInfoIdentPair lhs, TypeInfoIdentPair rhs)
		{ return lhs.Equals(rhs); }

		public static bool operator !=(TypeInfoIdentPair lhs, TypeInfoIdentPair rhs)
		{ return !(lhs.Equals(rhs)); }
	}
}

